package ua.kharkov.khpi.tarasenko.bach.web.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import ua.kharkov.khpi.tarasenko.bach.core.Constant;
import ua.kharkov.khpi.tarasenko.bach.core.LocalizedResourceManager;
import ua.kharkov.khpi.tarasenko.bach.core.user.Role;
import ua.kharkov.khpi.tarasenko.bach.core.user.UserBean;

/**
 * Filter that checks if user has access to requested resource.
 * 
 * @author oleksandr
 * 
 */
public class CommandAccessFilter implements Filter {
	private static final Logger log = Logger
			.getLogger(CommandAccessFilter.class);

	// Init parameters
	private final static String PARAMETER_ROLE_ADMIN = "admin";
	private final static String PARAMETER_ROLE_USER = "user";
	private final static String PARAMETER_ROLE_NON_ROLE = "non_role";
	private final static String PARAMETER_ROLE_ANY_ROLE = "any_role";
	private final static String PARAMETER_ROLE_BANNED = "banned";

	/*
	 * Allowed command for each role.
	 */
	private static Map<Role, List<String>> allowedCommandsForRoles = new HashMap<Role, List<String>>();
	private static List<String> any_role_commands = new ArrayList<String>();
	private static List<String> non_role_commands = new ArrayList<String>();

	@Override
	public void destroy() {
		if (log.isDebugEnabled()) {
			log.debug("Desrtaction started!");
		}

		// do nothing
		if (log.isDebugEnabled()) {
			log.debug("Desrtaction finished!");
		}
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {
		if (isAccessAllowed(req)) {
			if (log.isTraceEnabled()) {
				log.trace("Access allowed. Filter finished.");
			}
			chain.doFilter(req, resp);
		} else {

			// Create LocalizedResourceManager to get localized error messages
			LocalizedResourceManager LRManager = getLocalizedResourceManager((HttpServletRequest) req);

			String errorMessage = LRManager
					.getMessage("error_message.perm_denied");
			req.setAttribute(Constant.ATTRIBUTE_ERROR_MESSAGE, errorMessage);
			if (log.isTraceEnabled()) {
				log.trace("Access denied. Set the request attribute "
						+ Constant.ATTRIBUTE_ERROR_MESSAGE + " = "
						+ errorMessage + ". Filter finished.");
			}
			req.getRequestDispatcher(Constant.COMMAND_ERROR_PROCESSING)
					.forward(req, resp);
		}
	}

	@Override
	public void init(FilterConfig config) throws ServletException {
		if (log.isDebugEnabled()) {
			log.debug("Initialization started!");
		}
		allowedCommandsForRoles.put(Role.ADMIN,
				asList(config.getInitParameter(PARAMETER_ROLE_ADMIN)));
		allowedCommandsForRoles.put(Role.BANNED,
				asList(config.getInitParameter(PARAMETER_ROLE_BANNED)));
		allowedCommandsForRoles.put(Role.USER,
				asList(config.getInitParameter(PARAMETER_ROLE_USER)));
		if (log.isTraceEnabled()) {
			log.trace("allowedCommandsForRoles = " + allowedCommandsForRoles);
		}
		non_role_commands = asList(config
				.getInitParameter(PARAMETER_ROLE_NON_ROLE));
		if (log.isTraceEnabled()) {
			log.trace("non_role_commands = " + non_role_commands);
		}
		any_role_commands = asList(config
				.getInitParameter(PARAMETER_ROLE_ANY_ROLE));
		if (log.isTraceEnabled()) {
			log.trace("any_role_commands = " + any_role_commands);
		}
		if (log.isDebugEnabled()) {
			log.debug("Initialization finished!");
		}
	}

	/**
	 * Check if access to command is allowed for this user.
	 * 
	 * @param req
	 *            - request.
	 * @return true - if access to command is allowed, and false - if not.
	 */
	private boolean isAccessAllowed(ServletRequest req) {
		String commandName = req.getParameter(Constant.ATTRIBUTE_COMMAND);
		if (log.isDebugEnabled()) {
			log.debug("Get attribute " + Constant.ATTRIBUTE_COMMAND + " = "
					+ commandName);
		}
		if ((commandName == null) || (commandName.isEmpty())) {
			return false;
		}
		if (non_role_commands.contains(commandName)) {
			return true;
		}
		HttpServletRequest httpReq = (HttpServletRequest) req;
		HttpSession session = httpReq.getSession(false);
		if (session == null) {
			return false;
		}
		UserBean userBean = (UserBean) session
				.getAttribute(Constant.ATTRIBUTE_USER_BEAN);
		if (userBean == null) {
			return false;
		}
		if (log.isTraceEnabled()) {
			log.trace("Request from user with id " + userBean.getUserId()
					+ ", and name " + userBean.getUserFirstName());
		}
		Role userRole = userBean.getUserRole();
		if (userRole == null) {
			return false;
		}
		return allowedCommandsForRoles.get(userRole).contains(commandName)
				|| any_role_commands.contains(commandName);
	}

	/**
	 * Converts string with parameter values to list.
	 * 
	 * @param string
	 *            - string with values to convert.
	 * @return list with values.
	 */
	private List<String> asList(String string) {
		if (string == null) {
			return Collections.emptyList();
		}
		List<String> list = new ArrayList<String>();
		StringTokenizer st = new StringTokenizer(string);
		while (st.hasMoreTokens()) {
			list.add(st.nextToken());
		}
		return list;
	}

	/**
	 * Return Localized Resource Manager for preferable locale from session
	 * (Constant.ATTRIBUTE_PREFERABLE_LOCALE). If it isn't specified Locale will
	 * be taken from request
	 * 
	 * @param request
	 *            ;
	 * @return LRManager - LocalizedResourceManager;
	 * 
	 */
	protected LocalizedResourceManager getLocalizedResourceManager(
			HttpServletRequest request) {

		HttpSession session = request.getSession(false);
		Object localeObj = null;
		if (session != null) {
			localeObj = session
					.getAttribute(Constant.ATTRIBUTE_PREFERABLE_LOCALE);
		}

		Locale locale = null;
		if (localeObj != null) {
			locale = new Locale((String) localeObj);
		} else {
			// Get Locale from request
			locale = request.getLocale();
		}

		// Create LocalizedResourceManager to get localized error messages
		LocalizedResourceManager LRManager = new LocalizedResourceManager(
				locale);

		return LRManager;
	}
}
